v0.3.2 — spatial filters and vector search

The Django admin,
rebuilt for FastAPI.

Every FastAPI project rebuilds the same things: an admin panel, a sign-in form, password hashing, a token endpoint, refresh rotation, CSRF, an upload field that cannot be tricked. Django ships that. FastAPI does not. FastFort does — and installs as one wheel, with no Node.js, no build step and no CDN.

uv add "fastfort[sqlalchemy,postgres]"

Python 3.11+ · SQLite, PostgreSQL, MySQL · SQLAlchemy 2.0 or Tortoise · MIT

1

Mount it

One object, configured once. FastFort never opens a connection of its own — it takes the session factory the project already built.

main.py
from fastapi import FastAPI
from fastfort import FastFort, FastFortSettings
from fastfort.orm.sqlalchemy import SQLAlchemyBackend

from app.db import Base, session_factory
from app.models import User

app = FastAPI()

fort = FastFort(
    settings=FastFortSettings(project_name="Shop"),
    backend=SQLAlchemyBackend(session_factory=session_factory, base=Base),
)
fort.set_user_model(User, identity_field="email")
fort.autodiscover("app")
fort.mount(app)
2

Describe a model

Django's vocabulary, validated at start-up against the real model. Then open /admin: list, search, filters, sortable columns, pagination, bulk actions, and working create, edit and delete pages.

app/shop/admin.py
from fastfort import admin

from app.models import Product


@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    list_display = ("id", "name", "category", "price", "stock", "is_active")
    list_filter = ("is_active", "category", "price", "released_on")
    search_fields = ("sku", "name", "description")
    ordering = ("-created_at",)
    select_related = ("category",)
    icon = "box"

    actions = ("delete", "archive")

    @admin.action("Archive", icon="box")
    async def archive(self, adapter, objects):
        for product in objects:
            await adapter.update(product, {"is_active": False})
        return f"{len(objects)} products archived."

Every declaration above has a visible consequence. See each one, screen by screen →

What is in the wheel

All of it, on install. There is no paid tier and no second package to add.

Django-style model admin

@admin.register, list_display, list_filter, search_fields, ordering, actions. Every name is checked against the model when the admin is built, so a typo is a start-up error listing all of them — not a 500 the first time somebody opens that page.

Deletes you can trust

The confirmation walks the graph before anything is written and counts what actually goes: rows that cascade, rows kept with the link cleared, and rows that block the delete outright — refused with a sentence rather than a constraint violation.

A UI you will not want to replace

Light and dark, brand colour from one number, ⌘K command palette, full keyboard navigation and a real mobile layout. Every control works with JavaScript switched off; the script upgrades them and gets out of the way.

Production-grade auth

Argon2id hashing, JWT access and refresh, token rotation with reuse detection, login lockout with a growing delay, CSRF on every form, and a CSP that starts at default-src 'none'.

Every column type

Text, money, durations, UUIDs, JSON, hstore, arrays, enums, ranges and multiranges, inet/cidr/macaddr, bit strings — each with a real control, real validation and a filter where one makes sense. register_type adds your own.

PostGIS, drawn and edited

All seven geometry kinds on a hand-rolled slippy map — point, line, polygon with holes, and the multi-shapes. Spatial filters: within, intersects, and “5 km from here”, in metres on a geography column.

Vector search

pgvector columns ranked by similarity. ?embedding__near=[…] with cosine, L2, L1 or inner product, a neighbour count and a distance bound — an ordering that beats whatever else the page was asked for.

Eleven languages, shipped

English, Uzbek, Russian, Turkish, German, French, Spanish, Chinese, Japanese, Korean and Arabic — the last right-to-left, which turns the whole layout around from one attribute. The catalogues are in the wheel; there is nothing to configure.

Export and import

The current view out as CSV, Excel or JSON with filters and ordering intact — and back in again through the same parsers the form uses, every bad cell reported at once with its line number, and nothing written unless the whole file parses.

Two ORMs, one admin

SQLAlchemy 2.0 and Tortoise behind one adapter contract, with a conformance suite that asks both the same questions over identical models — so “the second one behaves the same” is a test rather than a claim.

A CLI that matters

createsuperuser, so a fresh install has a way in. check --deploy, which exits non-zero on a configuration that is not safe to expose. generate-secret, which prints and never writes.

No Node.js. None.

The CSS and JavaScript are hand-written files inside the package, served Brotli-compressed with a gzip fallback. 24 KB of CSS and 29 KB of script on the wire, and a size budget in the test suite that fails when it grows.

Fifteen minutes to a working admin

The quickstart goes from an empty directory to a signed-in admin with a real model on the page. No scaffolding tool, no generated files to read.