Split the model admin into a list and a page per model

/admin/models rendered a full edit form for every model. With eight that
was merely long; with a hundred it was unusable, which is the report.

The list is now compact rows only -- avatar, name, badges, position,
reorder buttons, Edit link -- with search across id and display name,
filter tabs (All / Enabled / Disabled / Pinned / Restricted, each with a
count), a connection filter, and pagination at 40. Filters are links, so
a filtered view is a real URL you can keep. Editing moved to
/admin/models/{id}/edit, one model per page, with Previous/Next links so
a freshly imported connection can be tidied without returning to the
list each time.

Measured with 128 models: the list is 73 KB showing 40 rows over 4
pages, and a detail page is 17 KB. The old page would have rendered all
128 forms into one response.

Reordering needed rethinking at that size too. Up/down is fine for
nudging a model one place but hopeless for moving it sixty, so the
detail page has a position field you type into; the value is clamped and
a non-numeric one is ignored rather than throwing. The move buttons take
a `back` field so they return to whatever filtered, paginated view they
were pressed on instead of dumping you at page 1.

Also adds a select-all checkbox for the bulk bar, scoped to a container
selector rather than the page so a future list can carry more than one.

212 tests, ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jaroslav Beneš
2026-07-21 13:43:00 +02:00
parent 29db54960e
commit 75edae039b
7 changed files with 702 additions and 162 deletions
+183
View File
@@ -477,3 +477,186 @@ def test_bulk_with_nothing_selected_is_harmless(client: TestClient, db, register
assert client.post(
"/admin/models/bulk", data={"action": "disable"}, follow_redirects=False
).status_code == 303
# --- The model admin list ----------------------------------------------------
def _many_models(db, count: int) -> None:
connection = _connection(db)
db.add_all(
[
Model(connection_id=connection.id, model_id=f"model-{i:03d}", position=i)
for i in range(count)
]
)
db.commit()
def test_the_list_paginates_rather_than_rendering_everything(
client: TestClient, db, registered
):
"""A hundred models must not become a hundred forms on one page."""
from lembas.api.admin_models import PAGE_SIZE
_many_models(db, PAGE_SIZE + 15)
page = client.get("/admin/models").text
assert page.count('name="model_ids"') == PAGE_SIZE
assert f"of {PAGE_SIZE + 15}" in page
assert "Page 1 of 2" in page
def test_the_second_page_shows_the_remainder(client: TestClient, db, registered):
from lembas.api.admin_models import PAGE_SIZE
_many_models(db, PAGE_SIZE + 15)
page = client.get("/admin/models?page=2").text
assert page.count('name="model_ids"') == 15
def test_an_out_of_range_page_is_clamped(client: TestClient, db, registered):
_many_models(db, 5)
assert "Page 1 of 1" not in client.get("/admin/models?page=99").text
assert client.get("/admin/models?page=99").status_code == 200
def test_the_list_does_not_render_edit_forms(client: TestClient, db, registered):
"""The whole point of the split: rows link to a page, they are not forms."""
_many_models(db, 3)
page = client.get("/admin/models").text
assert 'name="system_prompt"' not in page
assert 'name="display_name"' not in page
assert page.count("/edit") >= 3
def test_search_narrows_the_list(client: TestClient, db, registered):
connection = _connection(db)
db.add_all(
[
Model(connection_id=connection.id, model_id="llama-large", position=0),
Model(connection_id=connection.id, model_id="qwen-small", position=1),
]
)
db.commit()
page = client.get("/admin/models?q=qwen").text
assert "qwen-small" in page
assert "llama-large" not in page
def test_search_matches_the_display_name_too(client: TestClient, db, registered):
connection = _connection(db)
db.add(
Model(connection_id=connection.id, model_id="abc-123", display_name="Friendly Name")
)
db.commit()
assert "abc-123" in client.get("/admin/models?q=friendly").text
def test_filter_tabs_narrow_the_list(client: TestClient, db, registered):
connection = _connection(db)
db.add_all(
[
Model(connection_id=connection.id, model_id="on-model", position=0),
Model(connection_id=connection.id, model_id="off-model", position=1, enabled=False),
]
)
db.commit()
disabled = client.get("/admin/models?filter=disabled").text
assert "off-model" in disabled
assert ">on-model" not in disabled
def test_filtering_by_connection(client: TestClient, db, registered):
first = _connection(db)
second = _connection(db)
db.add_all(
[
Model(connection_id=first.id, model_id="from-first", position=0),
Model(connection_id=second.id, model_id="from-second", position=1),
]
)
db.commit()
page = client.get(f"/admin/models?connection={second.id}").text
assert "from-second" in page
assert "from-first" not in page
# --- The model detail page ---------------------------------------------------
def test_the_detail_page_carries_the_full_form(client: TestClient, db, registered):
_many_models(db, 3)
db.add(Group(name="Insiders"))
db.commit()
model = db.scalar(select(Model).where(Model.model_id == "model-001"))
page = client.get(f"/admin/models/{model.id}/edit").text
for field in ('name="display_name"', 'name="system_prompt"', 'name="capability"',
'name="group_ids"', 'name="position"'):
assert field in page, field
def test_the_detail_page_links_to_its_neighbours(client: TestClient, db, registered):
_many_models(db, 3)
first, middle, last = db.scalars(select(Model).order_by(Model.position)).all()
page = client.get(f"/admin/models/{middle.id}/edit").text
assert f"/admin/models/{first.id}/edit" in page
assert f"/admin/models/{last.id}/edit" in page
assert "2 of 3" in page
def test_an_unknown_model_detail_is_404(client: TestClient, db, registered):
assert client.get("/admin/models/nope/edit").status_code == 404
def test_saving_from_the_detail_page_returns_to_it(client: TestClient, db, registered):
_many_models(db, 2)
model = db.scalar(select(Model).where(Model.model_id == "model-000"))
response = client.post(
f"/admin/models/{model.id}",
data={"display_name": "Renamed", "enabled": "true", "public": "true"},
follow_redirects=False,
)
assert response.headers["location"].startswith(f"/admin/models/{model.id}/edit")
db.refresh(model)
assert model.display_name == "Renamed"
def test_typing_a_position_moves_the_model(client: TestClient, db, registered):
"""Up/down is unusable for moving a model 60 places."""
_many_models(db, 5)
last = db.scalar(select(Model).where(Model.model_id == "model-004"))
client.post(
f"/admin/models/{last.id}",
data={"display_name": "", "enabled": "true", "public": "true", "position": "1"},
)
order = [m.model_id for m in db.scalars(select(Model).order_by(Model.position))]
assert order[0] == "model-004"
def test_a_nonsense_position_is_ignored(client: TestClient, db, registered):
_many_models(db, 3)
model = db.scalar(select(Model).where(Model.model_id == "model-000"))
client.post(
f"/admin/models/{model.id}",
data={"display_name": "", "enabled": "true", "public": "true", "position": "abc"},
)
db.refresh(model)
assert model.position == 0
def test_moving_returns_to_the_filtered_view(client: TestClient, db, registered):
_many_models(db, 3)
model = db.scalar(select(Model).where(Model.model_id == "model-001"))
response = client.post(
f"/admin/models/{model.id}/move",
data={"direction": "up", "back": "/admin/models?filter=enabled&page=2"},
follow_redirects=False,
)
assert response.headers["location"] == "/admin/models?filter=enabled&page=2"