Таблицы и поля

Настройки Admin

Все аргументы Admin(...) передаются по имени. Настройте экземпляр до обработки запросов.

Аргумент Значение по умолчанию и назначение
auth, secret_key Обязательные backend авторизации и постоянный случайный секрет не короче 32 байт; см. авторизацию
adapters (); адаптеры существующих данных с включённым автообнаружением
title, description "Quite Admin", ""; название приложения и необязательное описание
language "en"; "en" или "ru" для встроенного интерфейса
theme, logo None; стандартные Theme() и логотип либо своя палитра и путь к локальному изображению; см. оформление
templates_dir None; каталог шаблонов приложения с приоритетом над встроенными
secure_cookies True; False допустим только для локального HTTP
session_cookie "quite_admin_session"; разным админкам одного домена задайте разные имена
session_max_age 28800 секунд (8 часов); положительное время жизни сессии, ревизий и курсоров
logger, log_structured None, False; логирование под управлением приложения
hooks None; необязательные OperationHooks для жизненного цикла записи
storage None; URL окружения или локальная SQLite либо явно заданный путь, URL или AdminStore; см. хранилище
history_enabled True; запись и отображение истории действий
history_level "changes"; завершённые изменения; "audit" добавляет авторизацию, экспорт и ошибки, "all" — чтения
history_retention_days 90; срок хранения в днях, None отключает очистку; подробности
history_permission None; необязательный sync/async callback доступа к истории и поиску пользователей
query_timeout None; положительный лимит секунд на чтение админки, включая очередь и ожидание async-пула
max_concurrent_reads None; положительное целое ограничение одновременных чтений этой админки в одном процессе
schema_auto_limit True; большие схемы БД открываются без выбранных таблиц; начальный выбор
schema_table_limit 100; положительный целочисленный порог доступных зарегистрированных таблиц для начального выбора схемы

add_adapter(adapter, discover=True, namespace=None) подключает источник данных,
register(model, adapter=..., options=...) выбирает и настраивает модель,
configure(slug, options=...) настраивает обнаруженную таблицу. ModelOptions заменяет
настройки таблицы целиком: указывайте все значения, которые хотите сохранить.
Подключайте через mount_fastapi(app, "/admin"), mount_litestar(app, "/admin")
или litestar_route("/admin"); см. интеграцию.

Регистрация таблиц

Автообнаружение можно отключить и зарегистрировать только нужные модели:

from quite_admin import Admin, FieldOptions, ModelOptions

admin = Admin(auth=auth_backend, secret_key=session_secret)
admin.add_adapter(adapter, discover=False)
admin.register(User, options=ModelOptions(
    label="Пользователи",
    list_fields=("id", "email", "is_active"),
    edit_fields=("email", "is_active", "created_at"),
    create_fields=("email", "is_active"),
    readonly_fields=("created_at",),
    exclude=("password_hash",),
    search_fields=("email",),
    filter_fields=("is_active", "created_at"),
    fields={"email": FieldOptions(label="Email", help_text="Адрес для уведомлений")},
    can_create=False,
    can_delete=False,
))

ModelOptions работает со всеми адаптерами.
У существующей записи одна страница просмотра и редактирования. Без права изменения поля
доступны только для чтения, а прямой POST отклоняется сервером. edit_fields определяет
порядок и набор изменяемых полей; остальные видимые поля остаются на странице только для чтения.
Нажатие на неинтерактивную часть строки открывает запись. Ссылка поддерживает клавиатуру
и открытие в новой вкладке. Карандаши, переключатели, флажки выбора и ссылки на связанные
записи или другие страницы выполняют своё действие. Удаление ведёт на подтверждение.
Возврат из записи сохраняет исходный список; см. обзор интерфейса.
Без права просмотра/изменения ссылки на запись нет; удаление зависит от соответствующего права.
При включённом автообнаружении register() переопределяет настройки модели.
Для reflection используйте admin.configure("table_name", options=...) без импорта модели.

Настройки полей

Параметр Поведение
slug, label Имя таблицы в URL и интерфейсе; по умолчанию берутся из схемы адаптера
list_fields Столбцы списка; по умолчанию первые шесть видимых полей
edit_fields Порядок и набор изменяемых полей существующей записи
create_fields Порядок и набор полей создания
readonly_fields Показывает значения, запрещая запись
exclude Скрывает поля и запрещает запись и поиск по ним
search_fields Строковые поля текстового поиска
filter_fields Видимые скалярные фильтры; None обнаруживает поддерживаемые поля, () отключает их
relations, readonly_relations Доступные редакторы связей и связи только для чтения
record_label Поле подписи записи в выборе связей
fields Подписи, подсказки и read-only настройки отдельных полей
can_create, can_edit, can_delete Разрешает или запрещает операции
permission Sync/async callback (request, identity, action); см. права
expose_sensitive_fields Явные исключения из автоматического скрытия секретных имён; exclude имеет приоритет
writable_protected_fields Явные исключения из защиты служебных полей и привилегий
group Необязательное непустое название группы каталога на главной, поиска таблиц и схемы БД
list_editable None включает доступные столбцы, кортеж выбирает поля, () отключает inline-правки
pagination "offset" для точного количества и номеров страниц либо "cursor" для обхода с сортировкой без COUNT
input_fields Кортеж Field только для форм, значения которых обрабатывает handler/сервис
create_schema, update_schema Необязательные модели Pydantic 2 для проверки переданных значений
create_handler, update_handler Сервисы модели вместо нативного создания/редактирования; см. сервисы и схемы
actions Кортеж кнопок RecordAction с проверкой прав на странице записи
bulk_actions (); команды RecordAction для конкретной таблицы, доступные только в меню иконки справа в списке; массовые действия
bulk_delete True; массовое удаление на странице с обычной проверкой delete; массовые действия

FieldOptions(read_only=True) тоже запрещает изменение поля.
Опечатка в настройке имени поля вызывает ошибку конфигурации.

По умолчанию скрываются имена с password, имена secret, token, credentials,
authorization, api_key, private_key, session_key, а также окончания _token
и _secret. exclude всегда имеет приоритет. Эвристика не находит все секреты —
проверяйте обнаруженную схему. Для намеренного исключения используйте
expose_sensitive_fields=("api_key",).

Поля created_at, updated_at, deleted_at, created_by, updated_by, is_admin,
is_superuser, is_staff, roles, permissions по умолчанию только для чтения.
Явно разрешить изменение можно через writable_protected_fields=("is_admin",).
Это не отменяет ограничения readonly_fields, generated и read-only метаданные адаптера.
Скрытие PK не делает идентификатор секретным: ключ закодирован в URL записи.

Собственные контролы полей

FieldOptions(display="url"), display="json" и display="multiline" добавляют встроенные контролы значений.
url_template="https://short.example/{value}" собирает ссылку из скалярного значения
только для отображения, без formatter. Подробности: безопасные URL,
копирование, read-only и сохранение прежних правил записи/экспорта.

FieldOptions принимает kind, parser, formatter, template, подпись и подсказку.
Пример счётчика #7:

FieldOptions(
    kind="string",
    parser=lambda raw: int(raw.removeprefix("#")),
    formatter=lambda value: "#" + str(value),
    template="counter.html",
)

Parser возвращает нативное значение или выбрасывает ValueError. Formatter возвращает
текст, который экранируется шаблонами. Шаблон получает field, values, errors
и общий контекст. Контрол должен иметь имя {{ field.name }} и значение
values[field.name]. Он включается только для изменяемого поля; серверный allowlist,
подпись и отображение ошибок сохраняются. Неизвестные типы адаптера остаются read-only,
пока свой адаптер явно не опишет поддерживаемый тип/codec. См. расширения.

Первичный ключ нельзя менять после создания. Generated и computed поля не редактируются.
Binary и неизвестные типы показываются только для чтения. Неавтоматический первичный
ключ можно задать при создании записи.

Неизвестные, скрытые и read-only поля в POST отклоняются с 422.
Сервер проверяет allowlist независимо от браузерной формы.

Значения по умолчанию, NULL и типы

Для поля с default форма создания предлагает Использовать значение по умолчанию модели / базы данных.
Снимите отметку, чтобы передать своё значение.
Пустое nullable-поле означает NULL; пустая строка NOT NULL означает "".

Поддержаны строки, целые, bool, float, Decimal, UUID, ISO date/time/datetime,
JSON и enum. Внешние ключи показываются как скалярные поля с подсказкой о целевой таблице,
если их не заменяет доступный редактор связи.

Если скрытое или read-only обязательное поле не имеет default, отключите создание
либо используйте адаптер, заполняющий поле через сервис приложения.

Несколько источников данных

admin.add_adapter(sqlalchemy_adapter, namespace="billing")
admin.add_adapter(tortoise_adapter, namespace="catalog")
# /admin/models/billing-orders/ и /admin/models/catalog-products/

При нескольких адаптерах передавайте adapter=adapter в register().
Конфликтующие имена требуют namespace или ModelOptions(slug=...).

Конкурирующее редактирование

Встроенные адаптеры атомарно отклоняют устаревшую форму: если запись или редактируемая
связь изменилась после открытия страницы, сохранение возвращает 409. Введённые
значения остаются в форме. Нажмите Перечитать запись, чтобы получить актуальную
версию перед новой правкой. Миграция БД и дополнительная колонка версии не нужны.
Ревизия действует в пределах настроенного времени сессии и привязана к пользователю,
таблице и полному первичному ключу. В ней находятся keyed fingerprints, а не значения полей.

Стандартный шаблон добавляет токен автоматически. При обновлении своего form.html
вставьте в форму рядом с CSRF-полем:

{% if revision_token %}
<input type="hidden" name="__revision" value="{{ revision_token }}">
{% endif %}

HTTP-клиент должен сначала получить форму GET-запросом и передать её __revision
при редактировании. Отсутствующая, неверная или истёкшая ревизия также возвращает 409.
После конфликта нельзя просто подставлять свежую ревизию без просмотра актуальной
записи. Защита относится к сохранению формы; удаление и свои действия сохраняют
прежние правила. Свои адаптеры подключают CheckedAdapter;
остальные используют собственную политику записи.

Настройка filter_fields и фильтрация по связанным записям описаны в разделе фильтров.

Контролы типов БД

Заголовок записи, секции формы и действия размещены в одном адаптивном контейнере
по центру. Короткие поля занимают две колонки, когда хватает места; многострочный
текст, JSON и редакторы связей — всю ширину. Подписи остаются над контролами,
а подсказки и ошибки проверки — рядом с соответствующим полем. Пустые поля чисел,
UUID, JSON и пароля показывают подсказки формата: они не задают значение и не заменяют
подпись. Значения только для чтения собраны в отдельной секции. Панель сохранения
и отмены остаётся доступной при прокрутке длинной формы. Одиночный и множественный выбор записей описан в
разделе связей; существующий календарь даты/времени — ниже.

Тип БД/модели Встроенный контрол
CHAR / VARCHAR, строки с ограничением Однострочный input с обнаруженным maxlength
TEXT, Tortoise/Peewee TextField Многострочная textarea на всю ширину
DATE Календарь темы, локальное отображение, ISO; fallback нативного input
DATETIME / TIMESTAMP Общий календарь со временем, необязательными долями секунды и UTC offset
TIME Тот же компонент без календаря, с необязательными долями секунды и UTC offset
JSON / JSONB Моноширинный редактор, подсветка, статус синтаксиса, форматирование
Integer / Decimal / float Числовой input с целым или произвольным шагом
Boolean Тогл с явным nullable-состоянием; fallback select
Enum Select допустимых значений; для nullable есть пустой вариант
UUID Текстовый input с серверной проверкой UUID
Binary / неизвестный Read-only, кроме уже явно настроенных parser/template политик

Тип TEXT определяется и SQL reflection, и ORM-адаптерами; ORM не обязательна.
Для собственного адаптера Field.multiline — необязательные метаданные с default False.
Логический kind остаётся string: поиск, фильтрация и разбор значений не меняются.
String() без длины остаётся однострочным; многострочность задаёт именно TEXT/TextField.
Существующий FieldOptions.template сохраняет приоритет.

Подсветка JSON работает локально через текстовые узлы, без внешнего редактора.
Форматировать JSON сохраняет запись чисел, повторяющиеся ключи и экранирование строк,
не преобразуя числа через JavaScript float. Статус синтаксиса не заменяет серверную
проверку типа/nullable. Подсветка отключается после 200 000 символов, форматирование —
после 40 уровней вложенности; обычная textarea продолжает работать.

DATE, DATETIME, TIME, nullable boolean, TEXT и JSON доступны для изменения в Schedules
браузерного демо. Обзор интерфейса показывает форму и редактор ячеек.

Формы, фильтры листинга, inline-редактирование и история используют один календарь
в теме админки. В формах и inline-редактировании значения доступны сразу: DATE —
одно поле даты, DATETIME — дата и время рядом, TIME — поле времени. Компактный
календарь не дублирует ввод даты и не требует отдельного «Применить»: выбор дня,
Сегодня или Сбросить меняет поле и закрывает календарь. Запись сохраняется
кнопкой Создать запись / Сохранить изменения всей формы. Escape и нажатие снаружи
закрывают просмотр календаря без изменения поля. Страница при открытии не прокручивается.

В фильтрах остаются Применить / Отмена для черновика выбора. Условие Между
открывает одну панель с двумя границами, включая время, если оно есть у поля.
Сбросить очищает обе границы. Период истории включает полные дни UTC.
Дни календаря поддерживают стрелки, Home/End и Page Up/Down; Alt+Down открывает
календарь поля в форме.

Контролы даты/времени не переводят значение в часовой пояс браузера. Z или явное
смещение ±HH:MM сохраняется отдельно; пустое смещение означает naive-значение.
Раздел Дополнительно в формах (Точность и часовой пояс в фильтрах)
раскрывает поля смещения и долей секунды (до шести цифр), чтобы ограничения нативного календаря не обрезали микросекунды.
Открытие формы не нормализует и не обрезает исходный timestamp. Необычные ISO-форматы,
смещения и ошибочно введённые значения остаются в текстовом input. Без JavaScript
дата/время остаются ISO-input, JSON — обычной textarea. Вид календаря зависит от браузера
и ОС. Серверный разбор, ограничения модели, права полей, CSRF и подписанные revision
по-прежнему обязательны. Встроенный скрипт разрешается существующим CSP-хешем страницы.

Готовый пример шаблона и parser есть в разделе контролов полей.

Группы таблиц

Задайте ModelOptions(group="Продажи"), чтобы сгруппировать таблицы в виде
Список таблиц на главной, глобальном поиске и панели выбора схемы БД. Для SQL reflection:
admin.configure("orders", options=ModelOptions(group="Продажи")). Таблицы без группы
попадают в Другие таблицы в каталоге главной и поиске. Права проверяются до
группировки: пустых и недоступных групп нет.

На главной выберите Список таблиц, чтобы открыть полный каталог с изначально
раскрытыми группами. Нажатие на заголовок или Enter/Пробел сворачивает группу. Метка
показывает число таблиц; ссылка содержит подпись и техническое имя, если оно отличается.
Браузер запоминает выбранный вид главной отдельно для пользователя и mount админки;
сворачивание групп в Списке таблиц действует в пределах текущего посещения.
Вид Карточки содержит закреплённые и недавно открытые таблицы, инструменты и свои
страницы. Найти таблицу (Ctrl/⌘ K) ищет по подписи, техническому имени и группе
на любой странице после входа.

Без JavaScript на главной доступен нативный раскрывающийся блок Все таблицы со
свёрнутыми группами: раскройте его и группу для перехода к таблице. Слева полоса значков
ведёт к инструментам админки. Оба вида главной и закрепление описаны
в обзоре интерфейса.

Булевы поля используют тоглы с управлением с клавиатуры. Для nullable есть отдельная
кнопка «Нет значения», сохраняющая различие false и SQL NULL. Read-only поля остаются
неизменяемыми; без JavaScript доступен исходный select.

Редактирование ячеек списка

Нажмите карандаш изменяемого столбца для редактирования без перехода к записи.
Нажатие на неинтерактивное значение или область строки открывает запись. Ссылки на
связанные записи, URL/копирование, раскрытие значений, карандаши и переключатели
выполняют своё действие. Тогл сохраняет изменение сразу.
ModelOptions(list_editable=None) (default) включает все подходящие столбцы;
list_editable=("name", "active") ограничивает набор; list_editable=() отключает режим.
Столбец должен отображаться и быть разрешён edit_fields и политиками поля. Скрытые,
read-only, generated и первичные ключи не редактируются. Внешние ключи и поля с собственными
шаблонами редактируются на странице записи. Переиспользуются встроенные контролы типов.
Сохранение явное; Отмена/Escape отбрасывает локальное изменение.

Endpoint требует права list/edit, проверяет ровно одно поле и CSRF, запускает хуки записи
и сохраняет атомарную защиту от устаревших изменений. Конфликт версии возвращает 409,
ошибка значения — 422 без очистки редактора. После конфликта обновите список. Собственному
адаптеру для защиты от конфликтов нужен CheckedAdapter, как и для полной формы.
Без JavaScript можно использовать полную страницу записи.

Булевы ячейки списка тоже отображаются тоглами. Изменяемые сохраняются сразу через
защищённый inline endpoint, read-only тоглы отключены. Для nullable есть кнопка ∅
и состояние «Нет значения». При ошибке/конфликте состояние не меняется, показывается
сообщение. Повторные нажатия блокируются на время сохранения.

Большие таблицы и время запросов

Для большой таблицы задайте ModelOptions(pagination="cursor"). Режим поддерживают
все встроенные адаптеры. Он читает одну дополнительную запись для навигации вперёд/назад
без точного COUNT(*) и растущего OFFSET. Нажмите заголовок скалярного столбца, включая
дату, для сортировки по возрастанию или убыванию без дополнительных настроек. При
одинаковых значениях полный первичный ключ, в том числе составной, задаёт порядок по
возрастанию. NULL идёт последним в обоих направлениях. Без выбора столбца порядок
задаётся первичным ключом. Для JSON, binary и неизвестных типов курсорная сортировка
недоступна. По умолчанию pagination="offset" сохраняет точное количество и номера страниц.

Курсоры подписаны, истекают через настроенное время сессии и привязаны к таблице,
пользователю, фильтрам, поиску, порядку и размеру страницы. Изменение поиска, фильтра или сортировки
начинает новый обход. Это не снимок БД: конкурентные вставки и удаления могут менять
страницы. Экспорт включает все подходящие записи в пределах своих лимитов независимо
от текущего курсора.

Пользовательский адаптер может реализовать WindowAdapter.list_window(model, query, after=..., before=...) из quite_admin.pagination. Результат — RecordWindow(records, has_next, has_previous, next_key, previous_key) с полными типизированными ключами.
Допускается одна граница и нулевой offset. Укажите необязательную возможность
supports_window_sorting = True, если реализация принимает query.order_by: границы
и проекции должны включать поле сортировки и все части PK. При равных значениях PK
сортируется по возрастанию, NULL идёт последним. У существующих пользовательских
адаптеров без этой возможности сохраняется сортировка по PK.
Метод не входит в обязательный Adapter API.

SQL reflection и SQLAlchemy/SQLModel также принимают query_timeout=2.0 в конструкторе
адаптера. Положительное число секунд ограничивает чтение в SQLite, PostgreSQL и MySQL;
None отключает лимит. SQLite использует progress handler на время операции, PostgreSQL —
локальный statement timeout, MySQL — временный лимит SELECT. Перед возвратом соединения
в пул настройки восстанавливаются либо соединение инвалидируется; ошибка возвращает
безопасный 504. Транзакции записи работают по-прежнему. Ожидание пула и рендеринг Python
в этот лимит не входят; при необходимости настройте их в приложении.

Общие лимиты чтения

Настройте общую очередь и таймаут операции непосредственно в админке с любым адаптером:

admin = Admin(
    auth=auth_backend, secret_key=session_secret, adapters=[adapter],
    query_timeout=3.0,
    max_concurrent_reads=2,
)

Обе настройки по умолчанию равны None и сохраняют прежнее поведение. Положительный
таймаут включает ожидание слота, асинхронное получение соединения из пула и само чтение.
Превышение возвращает безопасный 504. Положительный целочисленный лимит параллелизма
общий для всех моделей/адаптеров этого Admin в одном процессе. Это не лимит между
воркерами: у каждого своя очередь. При общем пуле выбирайте лимит ниже его ёмкости,
оставляя соединения обычным запросам приложения.

Политика охватывает асинхронное обнаружение моделей, списки, курсорные страницы,
получение записи, чтение/подбор связей и экспорт, включая пакетные запросы. Она действует
только на чтения самой админки. Прямые вызовы адаптера, аутентификация, I/O сервисных
обработчиков и служебное хранилище сохраняют собственные политики. Записи и commit не
прерываются этим таймаутом. Сбой чтения истории после commit сохраняет успешный ответ
записи и выдаёт существующее безопасное предупреждение истории.

При отмене слот освобождается после очистки ресурсов адаптера. Синхронный поток сначала
заканчивает работу и возвращает соединение/слот, поэтому ответ 504 может прийти позже
таймаута. Для прерывания самого SQL также настройте query_timeout нативного
SQL/SQLAlchemy-адаптера, описанный выше. Асинхронные адаптеры, включая GINO, получают
отмену во время ожидания пула или выполнения запроса. Дополнительный класс-обёртка,
семафор и обработчик исключения таймаута не нужны.

Поля только для ввода в форме

Дополнительные поля формы не требуют фиктивных колонок в адаптере:

from quite_admin import Field, ModelOptions

admin.register(User, options=ModelOptions(
    input_fields=(
        Field("password1", kind="password", label="Пароль"),
        Field("password2", kind="password", label="Повторите пароль"),
    ),
    create_fields=("username", "password1", "password2"),
    edit_fields=("username",),
))

Для полей ввода действуют преобразование типов, обязательность и списки разрешённых
полей создания/редактирования. Имена должны быть уникальными и не совпадать с полями
модели. Подписи, подсказки и контролы задаются через обычный fields={...}. Явное
объявление поля ввода разрешает ввод чувствительного имени без expose_sensitive_fields.

Схема адаптера не меняется. Поля ввода недоступны в списках, сортировке, поиске,
фильтрах, экспорте, названиях записей, диаграмме, отпечатках ревизии и деталях истории.
Они не заполняются из сохранённой записи и скрыты на страницах только для чтения.
Выбирайте формы через create_fields / edit_fields.

kind="password" создаёт доступный password-контрол с autocomplete="new-password".
Пароль не подставляется и не возвращается после ошибок валидации/конфликтов, в том числе
в контексте пользовательских шаблонов. Обязательный пароль проверяется на пустую строку
на сервере. Поля модели с явно заданным таким типом также исключаются из отображения
данных и экспорта. Password-контрол имеет приоритет перед пользовательским шаблоном поля.

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

Предыдущая