Настройки

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

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

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

from fastfort import FastFortSettings

settings = FastFortSettings(
    project_name="Fastfort Freight",
    admin={"page_size": 25, "count_strategy": "capped"},
    ui={"accent_hue": 232, "timezone": "Asia/Tashkent"},
    security={"cookie_secure": True, "hsts_seconds": 31_536_000},
)

Из переменных окружения

Любое значение можно задать через переменные окружения, используя префикс FASTFORT_ и двойное подчёркивание для вложенности:

FASTFORT_SECRET_KEY=...
FASTFORT_DEBUG=false
FASTFORT_ADMIN__PAGE_SIZE=50
FASTFORT_ADMIN__COUNT_STRATEGY=capped
FASTFORT_SECURITY__COOKIE_SECURE=true
FASTFORT_UI__ACCENT_HUE=232

На каждой группе установлено extra="forbid", поэтому опечатка в ключе — это ошибка запуска, а не настройка, которая молча ничего не делает.

Верхний уровень

ПолеТипЗначение по умолчанию
secret_keySecretStrобязательноПодписывает сессии, CSRF-токены и JWT. Ротация ключа делает недействительными все три.
project_namestr"FastFort"Отображается в шапке и в заголовках страниц.
auth_urlstr"/auth"Куда монтируются маршруты входа и выхода.
debugboolFalseТрассировки ошибок и отключённое кеширование шаблонов. Никогда не включать в production.

У secret_key нет значения по умолчанию

Фреймворк, поставляющий значение по умолчанию, гарантирует, что какое-нибудь развёртывание будет работать именно с ним, поэтому сбой намеренно перенесён на этап запуска, где он заметен и дёшево исправляется. Валидатор отвергает три вещи:

  • Заглушку. Всё, что содержит change-me, your-secret, insecure, example, fastfort и ещё шесть слов — совпадение ищется в любом месте значения, потому что растянуть заглушку до минимальной длины — ровно то, что сделает торопящийся человек.
  • Меньше 32 символов.
  • Меньше 8 уникальных символов. Это отсекает aaaa… и 1234512345… — они проходят проверку длины, но почти не несут энтропии.
uv run fastfort generate-secret --export
# FASTFORT_SECRET_KEY=xK9v…

Выводится в консоль и больше никуда — никогда не записывается в файл. Секрет, который инструмент положил на диск, — это секрет, который рано или поздно попадёт в коммит.

admin — где живёт админка и сколько она отдаёт наружу

ПолеЗначение по умолчанию
url"/admin"Нормализуется к ведущему слэшу; корень сайта запрещён.
page_size20
page_size_choices(20, 50, 100)Если активного page_size нет в списке, он добавляется автоматически — поэтому проект, настроивший 25, увидит своё собственное значение.
max_page_size200Жёсткий потолок для ?ps=. Без него один запрос может запросить все строки таблицы разом.
count_strategy"exact"exact · capped · estimated.
count_cap10_000Где capped останавливает подсчёт; интерфейс отображает «10000+».
autocomplete_limit20Количество строк, которые может вернуть один запрос автодополнения.
export_limit50_000Жёсткий потолок на один экспорт.
export_chunk_size1_000Количество строк за один проход при потоковой передаче.
dashboard_days30Окно, которое использует каждый виджет дашборда, строящий график по времени, если не указано своё. Один индексированный подсчёт на день — это же число определяет, сколько запросов выполняет главная страница. 0 отключает эти графики.
signup_field""Колонка модели пользователя, хранящая дату создания аккаунта; при пустом значении определяется автоматически.

Зачем нужен export_limit

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

Выбор count_strategy

exact подсчитывает каждую подходящую строку, что на большой таблице занимает основное время запроса — подсчёт часто обходится дороже, чем сама страница строк, к которой он прилагается. capped останавливается на count_cap; estimated запрашивает у базы данных её собственную статистику. На таблицах больше нескольких сотен тысяч строк нужен именно capped.

ui — внешний вид

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

ПолеЗначение по умолчанию
accent_hue2550–360.
accent_chroma0.160–0.37.
theme"system"system · light · dark.
density"comfortable"comfortable · compact.
logo_url · favicon_urlNone
richtext_urlNoneСкрипт, который улучшает каждое поле richtext.
custom_css_urlNoneЗагружается после встроенной таблицы стилей.
map_tile_url""Пустое значение означает отсутствие карты.
map_attribution""
map_center"0, 0"Вид на весь мир, а не на чью-то столицу.
map_max_zoom19Самый глубокий уровень, для которого у источника тайлов есть изображения.
languageNoneNone — следует языку браузера.
locale_dirNoneСобственные файлы <language>.json, имеющие приоритет.
timezone"UTC"
environment_labelNoneОтображается в шапке.
environment_tone"warning"info · warning · danger.

map_tile_url по умолчанию выключен

Включить его значит, что админка будет загружать изображения с чужого сервера. Этот сервер узнаёт, какие строки просматриваются и примерно где они находятся, а у большинства сервисов тайлов на этот счёт есть условия использования. Указать URL — значит, что ваш проект заявляет, что прочитал их.

Хост добавляется в img-src политики CSP админки, поэтому это настройка, а не то, что может подставить шаблон.

map_max_zoom — свойство источника, а не виджета

19 — это предел, на котором заканчивается стандартный слой OpenStreetMap. Некоторые коммерческие растровые слои доходят до 22, и если такой настроить со значением по умолчанию, он будет загружать изображения на три уровня менее детально, чем мог бы, — размытая карта без всякой причины.

ui={"map_tile_url": "https://tiles.example.com/{z}/{x}/{y}.png", "map_max_zoom": 22}

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

richtext_url ничего не поставляет в комплекте

Редактор — это ваш выбор: CKEditor, TinyMCE, Quill или любой другой, на который у вас уже есть лицензия. FastFort отрисовывает textarea и помечает его атрибутом data-ff-richtext; в этой настройке указывается скрипт, который находит такие поля. None оставляет обычный textarea — это рабочий элемент управления, а не сломанный редактор.

Альтернативой было бы включить редактор в поставку, но это неверный компромисс: лишние полмегабайта редактора в wheel-пакете для каждого проекта, большинству из которых нужен другой редактор или вообще никакой. Если URL указывает на другой источник, этот источник добавляется в script-src — именно поэтому это настройка.

environment_label

ui={"environment_label": "PRODUCTION", "environment_tone": "danger"}

Так различие между двумя открытыми у кого-то окнами становится заметно без необходимости что-то читать.

security — куки, CSRF и заголовки

Значения по умолчанию — самые строгие. Ослабление любого из них требует явного изменения, которое будет видно при код-ревью.

ПолеЗначение по умолчанию
cookie_name"fastfort_session"
cookie_secureTrue
cookie_httponlyTrue
cookie_samesite"lax"none требует cookie_secure=True.
cookie_domain · cookie_pathNone · "/"
csrf_enabledTrue
csrf_header_name"X-CSRF-Token"
csrf_field_name"_csrf"
security_headersTrueCSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy.
hsts_seconds0
trust_forwarded_forFalse

trust_forwarded_for

Включайте это только за прокси, который перезаписывает заголовок. За прокси, который дописывает значение, или вообще без прокси клиент может подделать собственный адрес — и тогда записи о блокировках и аудите будут приписаны тому, кого выберет злоумышленник.

CSP

Начинается с default-src 'none' при script-src 'self' и без nonce. Отсюда следует: никаких инлайновых <script>, никаких инлайновых обработчиков событий, никакого CDN. Данные попадают в браузер через атрибуты data-.

Ровно две директивы проект может расширить, и каждую — ровно на один источник: img-src (через ui.map_tile_url) и script-src (через ui.richtext_url).

auth — токены, пароли и блокировки

ПолеЗначение по умолчанию
access_token_ttl900Секунды. Намеренно короткий срок.
session_ttl1_209_600Сессия браузера в админке — рабочий день не равен окну обновления API-токена.
refresh_token_ttl1_209_600
rotate_refresh_tokensTrue
revoke_family_on_reuseTrue
algorithm"HS256"HS256/384/512 · RS256 · EdDSA.
issuer · audienceNone
password_min_length10
password_reject_commonTrue
lockout_threshold5Количество неудачных попыток до блокировки учётной записи или адреса.
lockout_seconds60Базовая задержка; она растёт с каждой следующей неудачной попыткой.
lockout_window_seconds900
allow_password_changeTrueЕсли выключено, ни один пароль нельзя изменить через админку.
allow_superuser_password_changeTrueЕсли выключено, пароль суперпользователя может изменить только он сам.
allow_user_deleteTrueЕсли выключено, ни одну учётную запись нельзя удалить.
allow_superuser_deleteTrueЕсли выключено, суперпользователей нельзя удалить. Настройка, которая обычно нужна публичному демо.

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

media — загруженные файлы

ПолеЗначение по умолчанию
rootPath("media")Создаётся при первом использовании.
upload_limit10_000_000Байты.

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

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

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

Проверка развёртывания

uv run fastfort check --app main:fort --deploy

Возвращает строки, а не выбрасывает исключение, поэтому один запуск сообщает обо всех проблемах сразу:

Fastfort Freight · 17 model(s)
warn  debug=True exposes tracebacks and internal state. Set FASTFORT_DEBUG=false.
warn  security.cookie_secure=False sends the session cookie over plain HTTP.
      Set FASTFORT_SECURITY__COOKIE_SECURE=true and serve over HTTPS.
warn  auth.access_token_ttl is 7200s. A leaked access token stays valid that
      long; 900s is the recommended ceiling.

3 problem(s) found.

Завершается с ненулевым кодом выхода, поэтому им можно блокировать развёртывание. settings.require_production_ready() — та же проверка, но в виде исключения, для проекта, который хочет, чтобы запуск сразу завершался отказом.