Настройки
Вся поверхность конфигурации — каждая группа, каждое поле, значение по умолчанию и почему оно именно такое.
Доступно с версии 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_key | SecretStr | обязательно | Подписывает сессии, CSRF-токены и JWT. Ротация ключа делает недействительными все три. |
project_name | str | "FastFort" | Отображается в шапке и в заголовках страниц. |
auth_url | str | "/auth" | Куда монтируются маршруты входа и выхода. |
debug | bool | False | Трассировки ошибок и отключённое кеширование шаблонов. Никогда не включать в 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_size | 20 | |
page_size_choices | (20, 50, 100) | Если активного page_size нет в списке, он добавляется автоматически — поэтому проект, настроивший 25, увидит своё собственное значение. |
max_page_size | 200 | Жёсткий потолок для ?ps=. Без него один запрос может запросить все строки таблицы разом. |
count_strategy | "exact" | exact · capped · estimated. |
count_cap | 10_000 | Где capped останавливает подсчёт; интерфейс отображает «10000+». |
autocomplete_limit | 20 | Количество строк, которые может вернуть один запрос автодополнения. |
export_limit | 50_000 | Жёсткий потолок на один экспорт. |
export_chunk_size | 1_000 | Количество строк за один проход при потоковой передаче. |
dashboard_days | 30 | Окно, которое использует каждый виджет дашборда, строящий график по времени, если не указано своё. Один индексированный подсчёт на день — это же число определяет, сколько запросов выполняет главная страница. 0 отключает эти графики. |
signup_field | "" | Колонка модели пользователя, хранящая дату создания аккаунта; при пустом значении определяется автоматически. |
Зачем нужен export_limit
Экспорт выполняет текущий запрос без пагинации. Без ограничения случайный клик по экспортировать всё на таблице из десяти миллионов строк превращается в запрос, который никогда не завершится, и в базу данных, переставшую отвечать всем остальным.
Выбор count_strategy
exact подсчитывает каждую подходящую строку, что на большой таблице занимает
основное время запроса — подсчёт часто обходится дороже, чем сама страница
строк, к которой он прилагается. capped останавливается на count_cap;
estimated запрашивает у базы данных её собственную статистику. На таблицах
больше нескольких сотен тысяч строк нужен именно capped.
ui — внешний вид
Вся палитра выводится из одного оттенка в OKLCH. Ребрендинг — это число, а не пересборка: нет ни этапа сборки, ни таблицы стилей, которую нужно форкать.
| Поле | Значение по умолчанию | |
|---|---|---|
accent_hue | 255 | 0–360. |
accent_chroma | 0.16 | 0–0.37. |
theme | "system" | system · light · dark. |
density | "comfortable" | comfortable · compact. |
logo_url · favicon_url | None | |
richtext_url | None | Скрипт, который улучшает каждое поле richtext. |
custom_css_url | None | Загружается после встроенной таблицы стилей. |
map_tile_url | "" | Пустое значение означает отсутствие карты. |
map_attribution | "" | |
map_center | "0, 0" | Вид на весь мир, а не на чью-то столицу. |
map_max_zoom | 19 | Самый глубокий уровень, для которого у источника тайлов есть изображения. |
language | None | None — следует языку браузера. |
locale_dir | None | Собственные файлы <language>.json, имеющие приоритет. |
timezone | "UTC" | |
environment_label | None | Отображается в шапке. |
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_secure | True | |
cookie_httponly | True | |
cookie_samesite | "lax" | none требует cookie_secure=True. |
cookie_domain · cookie_path | None · "/" | |
csrf_enabled | True | |
csrf_header_name | "X-CSRF-Token" | |
csrf_field_name | "_csrf" | |
security_headers | True | CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy. |
hsts_seconds | 0 | |
trust_forwarded_for | False |
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_ttl | 900 | Секунды. Намеренно короткий срок. |
session_ttl | 1_209_600 | Сессия браузера в админке — рабочий день не равен окну обновления API-токена. |
refresh_token_ttl | 1_209_600 | |
rotate_refresh_tokens | True | |
revoke_family_on_reuse | True | |
algorithm | "HS256" | HS256/384/512 · RS256 · EdDSA. |
issuer · audience | None | |
password_min_length | 10 | |
password_reject_common | True | |
lockout_threshold | 5 | Количество неудачных попыток до блокировки учётной записи или адреса. |
lockout_seconds | 60 | Базовая задержка; она растёт с каждой следующей неудачной попыткой. |
lockout_window_seconds | 900 | |
allow_password_change | True | Если выключено, ни один пароль нельзя изменить через админку. |
allow_superuser_password_change | True | Если выключено, пароль суперпользователя может изменить только он сам. |
allow_user_delete | True | Если выключено, ни одну учётную запись нельзя удалить. |
allow_superuser_delete | True | Если выключено, суперпользователей нельзя удалить. Настройка, которая обычно нужна публичному демо. |
Каждое обновление выпускает новый токен и списывает старый. Повторное использование списанного токена означает, что он был украден, поэтому отзывается вся его семья токенов.
media — загруженные файлы
| Поле | Значение по умолчанию | |
|---|---|---|
root | Path("media") | Создаётся при первом использовании. |
upload_limit | 10_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() — та же проверка, но в
виде исключения, для проекта, который хочет, чтобы запуск сразу завершался
отказом.